/*
 * ClickSniffer.cc
 *
 * Sniffe click stream packet and remove the payload of other packets.
 *
 * Version 0.9 (2009 08 13)
 * Copyright (c) 2009, Iowa State University
 *
 * *********************************************************
 *
 * The code is based on the Siffer Example sniffex.c on www.tcpdump.org
 *
 */

#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <unistd.h>


// The captured packets are divided into files within 10 minutes interval
#define TIME_INTERVAL 600

// default capture length (maximum bytes per packet to capture)
#define LEN_CAP 1518

// ethernet headers are always exactly 14 bytes
#define SIZE_ETHERNET 14

// Ethernet addresses are 6 bytes
#define ETHER_ADDR_LEN	6

// Ethernet header
struct sniff_ethernet {
        u_char  ether_dhost[ETHER_ADDR_LEN];    // destination host address
        u_char  ether_shost[ETHER_ADDR_LEN];    // source host address
        u_short ether_type;                     // IP? ARP? RARP? etc
};

// IP header
struct sniff_ip {
        u_char  ip_vhl;                 		// version << 4 | header length >> 2
        u_char  ip_tos;                 		// type of service
        u_short ip_len;                 		// total length
        u_short ip_id;                  		// identification
        u_short ip_off;                 		// fragment offset field
        #define IP_RF 0x8000            		// reserved fragment flag
        #define IP_DF 0x4000            		// dont fragment flag
        #define IP_MF 0x2000            		// more fragments flag
        #define IP_OFFMASK 0x1fff       		// mask for fragmenting bits
        u_char  ip_ttl;                 		// time to live
        u_char  ip_p;                   		// protocol
        u_short ip_sum;                 		// checksum
        struct  in_addr ip_src,ip_dst;  		// source and dest address
};
#define IP_HL(ip)               (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip)                (((ip)->ip_vhl) >> 4)

// TCP header
typedef u_int tcp_seq;

struct sniff_tcp {
        u_short th_sport;               		// source port
        u_short th_dport;               		// destination port
        tcp_seq th_seq;                		 	// sequence number
        tcp_seq th_ack;                 		// acknowledgement number
        u_char  th_offx2;               		// data offset, rsvd
#define TH_OFF(th)      (((th)->th_offx2 & 0xf0) >> 4)
        u_char  th_flags;
        #define TH_FIN  0x01
        #define TH_SYN  0x02
        #define TH_RST  0x04
        #define TH_PUSH 0x08
        #define TH_ACK  0x10
        #define TH_URG  0x20
        #define TH_ECE  0x40
        #define TH_CWR  0x80
        #define TH_FLAGS        (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
        u_short th_win;                 		// window
        u_short th_sum;                 		// checksum
        u_short th_urp;                			// urgent pointer
};

int read_dump(const char * filename);

//---------------------------------main function---------------------------------
int main(int argc, char * argv[])
{
	unsigned int num_interval = 6;					// The number of capturing interval

	char dev[100];									// capture device name
	char errbuf[PCAP_ERRBUF_SIZE];					// error buffer
	memset(dev,0,100);
	memset(errbuf,0,PCAP_ERRBUF_SIZE);

	pcap_t *handle;									// packet capture handle

	char filter_exp[] = "tcp and port 80";			// filter expression
	struct bpf_program fp;							// compiled filter program (expression)
	bpf_u_int32 mask;								// subnet mask
	bpf_u_int32 net;								// ip

	struct pcap_pkthdr header;						// The header that pcap gives us
	const u_char *packet;							// The actual packet

	// declare pointers to packet headers
	const struct sniff_ethernet *ethernet;  		// The ethernet header
	const struct sniff_ip *ip;              		// The IP header
	int size_ip;									// The size of IP header
	const struct sniff_tcp *tcp;					// The TCP header
	int size_tcp;									// The size of TCP header
	char * payload;									// The pointer of payload
	int size_payload;								// The size of payload

	time_t cur_time, end_time;						// The boundary of time interval

	// create a dump file to save the packets
	pcap_dumper_t *dumpfile;						// The output file
	char filename[100];								// The file name of output
	memset(filename,0,100);

	// parse the options in the command line
	char c;
	while ((c = getopt(argc, argv, "d:h:r:")) != -1) {
			switch (c) {
			case 'd':
				strcpy(dev,optarg);
				break;
			case 'h':
				num_interval = atoi(optarg)*6;
				break;
			// read dump file
			case 'r':
				return read_dump(optarg);
			// ----------------------------------------------//
			default:
				break;
			}
	}

	// find a capture device if not specified in the command line
	if(strlen(dev) == 0){
		pcap_if_t *alldevs;
		pcap_if_t *d;

		// Retrieve the device list from the local machine
		if (pcap_findalldevs(&alldevs, errbuf) == -1)
		{
		    fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf);
		    return 1;
		}

		// Print the list
		int i=0;
		for(d= alldevs; d != NULL; d= d->next)
		{
		    printf("%d. %s", ++i, d->name);
		    if (d->description)
		        printf(" (%s)\n", d->description);
		    else
		        printf(" (No description available)\n");
		}

		if (i == 0)
		{
		    printf("\nNo interfaces found!\n");
		    return 0;
		}
		int inum = 0;
		printf("Enter the interface number (1-%d):",i);
		while(scanf("%d", &inum) != 1) {
			printf("Enter the interface number (1-%d):",i);
		}

		if(inum < 1 || inum > i)
		{
		   printf("\nInterface number out of range.\n");
		   // Free the device list
		   pcap_freealldevs(alldevs);
		   return -1;
		}

		// Jump to the selected adapter
		for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);

		if(strlen(d->name) < 100) strcpy(dev,d->name);
		else {
			fprintf(stderr, "The name is too long");
			return -1;
		}
		// Free the device list.
		pcap_freealldevs(alldevs);
	}

	// get network number and mask associated with capture device
	if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
		fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf);
		net = 0;
		mask = 0;
	}

	// open capture device
	handle = pcap_open_live(dev, LEN_CAP, 1, 1000, errbuf);
	if (handle == NULL) {
		fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
		return -1;
	}

	/* make sure we're capturing on an Ethernet device [2] */
	if (pcap_datalink(handle) != DLT_EN10MB) {
		fprintf(stderr, "%s is not an Ethernet\n", dev);
		return -1;
	}

	// compile the filter expression
	if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
		fprintf(stderr, "Couldn't parse filter %s: %s\n",
		    filter_exp, pcap_geterr(handle));
		return -1;
	}

	// apply the compiled filter
	if (pcap_setfilter(handle, &fp) == -1) {
		fprintf(stderr, "Couldn't install filter %s: %s\n",
		    filter_exp, pcap_geterr(handle));
		return -1;
	}

	// print capture info
	printf("\nDevice: %s\n", dev);
	printf("Filter expression: %s\n", filter_exp);
	printf("Time Length: %d hours %d minutes\n", num_interval/6, num_interval%6*10);

	for(int i=0; i<num_interval; i++) {
		//update the current time and end time
		cur_time = time(NULL);
		end_time = cur_time + TIME_INTERVAL;

		// Creat a new dump file for each interval
		tm * ptm = gmtime(&cur_time);
		if(ptm != NULL)
			sprintf(filename, "./%04d%02d%02d%02d%02d.dump",ptm->tm_year+1900,
						ptm->tm_mon+1,ptm->tm_mday,ptm->tm_hour,ptm->tm_min);
		else sprintf(filename, "./interval-%d.dump",i+1);
		dumpfile = pcap_dump_open(handle, filename);
		if(dumpfile==NULL) {
		    fprintf(stderr,"\nError opening output file.\n");
		    pcap_freecode(&fp);
		    pcap_close(handle);
		    return -1;
		}else printf("Current Capture File: %s\n",filename+2);

		//main loop to capture packets
		while(cur_time < end_time)
		{

			packet = pcap_next(handle, &header);
			if(packet == NULL) {
				cur_time = time(NULL);
			}
			else {
				// update current time to the capture time
				cur_time = header.ts.tv_sec;

				// define ethernet header
				ethernet = (struct sniff_ethernet*)(packet);

				// define/compute ip header offset
				ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
				if(IP_V(ip) == 4) {
					size_ip = IP_HL(ip)*4;
					if (size_ip > 19) {
						// determine protocol
						int hdr_len = 0;                    					// the size of protocol headers
						if(ip->ip_p == IPPROTO_TCP) {
							// define/compute tcp header offset
							tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
							size_tcp = TH_OFF(tcp)*4;

							// define/compute tcp payload (segment) offset
							payload = (char *)(packet + SIZE_ETHERNET + size_ip + size_tcp);

							// compute tcp payload (segment) size
							size_payload = ntohs(ip->ip_len) - (size_ip + size_tcp);

							// Build a filter to find click stream packets
							if(size_payload > 0) {								// the minimum possible size of the payload in a click packet
								char * key = strstr(payload,"GET");				// The action in the HTTP protocl
								if(key == payload) {
									// Search for URL
									char * url = strstr(key+3, "http://googleads.g.doubleclick.net/");
									// Search for click
									char * aclk = strstr(key+3, "aclk?");
									if(url != NULL && aclk != NULL) {
										//save the click stream packets
										pcap_dump((u_char *)dumpfile, &header, packet);
									}
									else {
										//remove the payload for other packets
										header.caplen = SIZE_ETHERNET + size_ip + size_tcp;
										pcap_dump((u_char *)dumpfile, &header, packet);
									}
								}
							}
							else pcap_dump((u_char *)dumpfile, &header, packet);
						}
					}
				}
			}
		}
		pcap_dump_close(dumpfile);
	}

	pcap_freecode(&fp);
	pcap_close(handle);
	printf("Capture complete.\n");
	return 0;
}

/* read offline dump file for the test of the click filter */

int read_dump(const char * filename) {
	pcap_t *handle;
	char errbuf[PCAP_ERRBUF_SIZE];

	struct pcap_pkthdr header;				// The header that pcap gives us
	const u_char *packet;					// The actual packet
	const struct sniff_ethernet *ethernet;  // The ethernet header
	const struct sniff_ip *ip;              // The IP header
	int size_ip;							// The size of IP header
	const struct sniff_tcp *tcp;            // The TCP header
	int size_tcp;							// The size of TCP header
	char *payload;                    		// Packet payload
	int size_payload;						// The size of payload

	// create a dump file to save the packets
	pcap_dumper_t *dumpfile;
	char dumpname[200];
	memset(dumpname,0,200);

	// Open the capture file
	if ( (handle= pcap_open_offline(
	  		filename,						//file name
	   		errbuf         					// error buffer
	   		) ) == NULL)
	{
	    fprintf(stderr,"\nUnable to open the file %s.\n", filename);
	    return -1;
	}

	// Open a dump file for the result
	strcpy(dumpname,"csniffer-");
	strcat(dumpname,filename);
	dumpfile = pcap_dump_open(handle, dumpname);
	if(dumpfile==NULL) {
	    fprintf(stderr,"\nError opening output file. %s", dumpname);
	    return -1;
	}

	packet = pcap_next(handle, &header);
	while(packet != NULL)
	{
		ethernet = (struct sniff_ethernet*)(packet);

		// define/compute ip header offset
		ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
		if(IP_V(ip) == 4) {
			size_ip = IP_HL(ip)*4;
			if (size_ip > 19) {
				// determine protocol
				int hdr_len = 0;                    // the size of protocol headers
				if(ip->ip_p == IPPROTO_TCP) {
					// define/compute tcp header offset
					tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
					size_tcp = TH_OFF(tcp)*4;
					if(ntohs(tcp->th_sport) == 80 || ntohs(tcp->th_dport) == 80 ) {
						// define/compute tcp payload (segment) offset
						payload = (char *)(packet + SIZE_ETHERNET + size_ip + size_tcp);

						// compute tcp payload (segment) size
						size_payload = ntohs(ip->ip_len) - (size_ip + size_tcp);

						// Build a filter to find click stream packets
						if(size_payload > 0) {								// the minimum possible size of the payload in a click packet
							char * key = strstr(payload,"GET");				// The action in the HTTP protocl
							if(key == payload) {
								// Search for URL
								char * url = strstr(key+3, "http://googleads.g.doubleclick.net/");
								// Search for click
								char * aclk = strstr(key+3, "aclk?");
								if(url != NULL && aclk != NULL) {
									//save the click stream packets
									pcap_dump((u_char *)dumpfile, &header, packet);
								}
								else {
									//remove the payload for other packets
									header.caplen = SIZE_ETHERNET + size_ip + size_tcp;
									pcap_dump((u_char *)dumpfile, &header, packet);
								}
							}
						}
						else pcap_dump((u_char *)dumpfile, &header, packet);
					}
				}
			}
		}
		packet = pcap_next(handle, &header);
	}
	pcap_dump_close(dumpfile);
	return 0;
}
